home *** CD-ROM | disk | FTP | other *** search
/ PC World Komputer 2010 April / PCWorld0410.iso / hity wydania / Ubuntu 9.10 PL / karmelkowy-koliberek-9.10-netbook-remix-PL.iso / casper / filesystem.squashfs / usr / share / pyshared / nevow / static.py < prev    next >
Text File  |  2006-07-17  |  13KB  |  428 lines

  1. # -*- test-case-name: twisted.test.test_web -*-
  2. # Copyright (c) 2004 Divmod.
  3. # See LICENSE for details.
  4.  
  5. """I deal with static resources.
  6. """
  7.  
  8. # System Imports
  9. import os, string, time
  10. import cStringIO
  11. import traceback
  12. import warnings
  13. StringIO = cStringIO
  14. del cStringIO
  15. from zope.interface import implements
  16.  
  17. # Sibling Imports
  18. from twisted.web import error
  19. from twisted.web.util import redirectTo
  20.  
  21. # Twisted Imports
  22. try:
  23.     from twisted.web import http
  24. except ImportError:
  25.     from twisted.protocols import http
  26. from twisted.python import threadable, log, components, filepath
  27. from twisted.internet import abstract
  28. from twisted.spread import pb
  29. from twisted.python.util import InsensitiveDict
  30. from twisted.python.runtime import platformType
  31.  
  32. from nevow import appserver, dirlist, inevow, rend
  33.  
  34.  
  35. dangerousPathError = error.NoResource("Invalid request URL.")
  36.  
  37. def isDangerous(path):
  38.     return path == '..' or '/' in path or os.sep in path
  39.  
  40. class Data:
  41.     """
  42.     This is a static, in-memory resource.
  43.     """
  44.     implements(inevow.IResource)
  45.  
  46.     def __init__(self, data, type, expires=None):
  47.         self.data = data
  48.         self.type = type
  49.         self.expires = expires
  50.  
  51.  
  52.     def time(self):
  53.         """
  54.         Return the current time as a float.
  55.  
  56.         The default implementation simply uses L{time.time}.  This is mainly
  57.         provided as a hook for tests to override.
  58.         """
  59.         return time.time()
  60.  
  61.  
  62.     def locateChild(self, ctx, segments):
  63.         return appserver.NotFound
  64.  
  65.  
  66.     def renderHTTP(self, ctx):
  67.         request = inevow.IRequest(ctx)
  68.         request.setHeader("content-type", self.type)
  69.         request.setHeader("content-length", str(len(self.data)))
  70.         if self.expires is not None:
  71.             request.setHeader("expires",
  72.                               http.datetimeToString(self.time() + self.expires))
  73.         if request.method == "HEAD":
  74.             return ''
  75.         return self.data
  76.  
  77. def staticHTML(someString):
  78.     return Data(someString, 'text/html')
  79.  
  80.  
  81. def addSlash(request):
  82.     return "http%s://%s%s/" % (
  83.         request.isSecure() and 's' or '',
  84.         request.getHeader("host"),
  85.         (string.split(request.uri,'?')[0]))
  86.  
  87. class Registry(components.Componentized):
  88.     """
  89.     I am a Componentized object that will be made available to internal Twisted
  90.     file-based dynamic web content such as .rpy and .epy scripts.
  91.     """
  92.  
  93.     def __init__(self):
  94.         components.Componentized.__init__(self)
  95.         self._pathCache = {}
  96.  
  97.     def cachePath(self, path, rsrc):
  98.         self._pathCache[path] = rsrc
  99.  
  100.     def getCachedPath(self, path):
  101.         return self._pathCache.get(path)
  102.  
  103.  
  104. def loadMimeTypes(mimetype_locations=['/etc/mime.types']):
  105.     """
  106.     Multiple file locations containing mime-types can be passed as a list.
  107.     The files will be sourced in that order, overriding mime-types from the
  108.     files sourced beforehand, but only if a new entry explicitly overrides
  109.     the current entry.
  110.     """
  111.     import mimetypes
  112.     # Grab Python's built-in mimetypes dictionary.
  113.     contentTypes = mimetypes.types_map
  114.     # Update Python's semi-erroneous dictionary with a few of the
  115.     # usual suspects.
  116.     contentTypes.update(
  117.         {
  118.             '.conf':  'text/plain',
  119.             '.diff':  'text/plain',
  120.             '.exe':   'application/x-executable',
  121.             '.flac':  'audio/x-flac',
  122.             '.java':  'text/plain',
  123.             '.ogg':   'application/ogg',
  124.             '.oz':    'text/x-oz',
  125.             '.swf':   'application/x-shockwave-flash',
  126.             '.tgz':   'application/x-gtar',
  127.             '.wml':   'text/vnd.wap.wml',
  128.             '.xul':   'application/vnd.mozilla.xul+xml',
  129.             '.py':    'text/plain',
  130.             '.patch': 'text/plain',
  131.             '.pjpeg': 'image/pjpeg',
  132.             '.tac':   'text/x-python',
  133.         }
  134.     )
  135.     # Users can override these mime-types by loading them out configuration
  136.     # files (this defaults to ['/etc/mime.types']).
  137.     for location in mimetype_locations:
  138.         if os.path.exists(location):
  139.             contentTypes.update(mimetypes.read_mime_types(location))
  140.             
  141.     return contentTypes
  142.  
  143. def getTypeAndEncoding(filename, types, encodings, defaultType):
  144.     p, ext = os.path.splitext(filename)
  145.     ext = ext.lower()
  146.     if encodings.has_key(ext):
  147.         enc = encodings[ext]
  148.         ext = os.path.splitext(p)[1].lower()
  149.     else:
  150.         enc = None
  151.     type = types.get(ext, defaultType)
  152.     return type, enc
  153.  
  154. class File:
  155.     """
  156.     File is a resource that represents a plain non-interpreted file
  157.     (although it can look for an extension like .rpy or .cgi and hand the
  158.     file to a processor for interpretation if you wish). Its constructor
  159.     takes a file path.
  160.  
  161.     Alternatively, you can give a directory path to the constructor. In this
  162.     case the resource will represent that directory, and its children will
  163.     be files underneath that directory. This provides access to an entire
  164.     filesystem tree with a single Resource.
  165.  
  166.     If you map the URL 'http://server/FILE' to a resource created as
  167.     File('/tmp'), then http://server/FILE/ will return an HTML-formatted
  168.     listing of the /tmp/ directory, and http://server/FILE/foo/bar.html will
  169.     return the contents of /tmp/foo/bar.html .
  170.     """
  171.  
  172.     implements(inevow.IResource)
  173.  
  174.     contentTypes = loadMimeTypes()
  175.  
  176.     contentEncodings = {
  177.         ".gz" : "application/x-gzip",
  178.         ".bz2": "application/x-bzip2"
  179.         }
  180.  
  181.     processors = {}
  182.  
  183.     indexNames = ["index", "index.html", "index.htm", "index.trp", "index.rpy"]
  184.  
  185.     type = None
  186.  
  187.     def __init__(self, path, defaultType="text/html", ignoredExts=(), registry=None, allowExt=0):
  188.         """Create a file with the given path.
  189.         """
  190.         self.fp = filepath.FilePath(path)
  191.         # Remove the dots from the path to split
  192.         self.defaultType = defaultType
  193.         if ignoredExts in (0, 1) or allowExt:
  194.             warnings.warn("ignoredExts should receive a list, not a boolean")
  195.             if ignoredExts or allowExt:
  196.                 self.ignoredExts = ['*']
  197.             else:
  198.                 self.ignoredExts = []
  199.         else:
  200.             self.ignoredExts = list(ignoredExts)
  201.         self.registry = registry or Registry()
  202.         self.children = {}
  203.  
  204.     def ignoreExt(self, ext):
  205.         """Ignore the given extension.
  206.  
  207.         Serve file.ext if file is requested
  208.         """
  209.         self.ignoredExts.append(ext)
  210.  
  211.     def directoryListing(self):
  212.         return dirlist.DirectoryLister(self.fp.path,
  213.                                        self.listNames(),
  214.                                        self.contentTypes,
  215.                                        self.contentEncodings,
  216.                                        self.defaultType)
  217.  
  218.     def putChild(self, name, child):
  219.         self.children[name] = child
  220.         
  221.     def locateChild(self, ctx, segments):
  222.         r = self.children.get(segments[0], None)
  223.         if r:
  224.             return r, segments[1:]
  225.         
  226.         path=segments[0]
  227.         
  228.         self.fp.restat()
  229.         
  230.         if not self.fp.isdir():
  231.             return rend.NotFound
  232.  
  233.         if path:
  234.             fpath = self.fp.child(path)
  235.         else:
  236.             fpath = self.fp.childSearchPreauth(*self.indexNames)
  237.             if fpath is None:
  238.                 return self.directoryListing(), segments[1:]
  239.  
  240.         if not fpath.exists():
  241.             fpath = fpath.siblingExtensionSearch(*self.ignoredExts)
  242.             if fpath is None:
  243.                 return rend.NotFound
  244.  
  245.         # Don't run processors on directories - if someone wants their own
  246.         # customized directory rendering, subclass File instead.
  247.         if fpath.isfile():
  248.             if platformType == "win32":
  249.                 # don't want .RPY to be different than .rpy, since that
  250.                 # would allow source disclosure.
  251.                 processor = InsensitiveDict(self.processors).get(fpath.splitext()[1])
  252.             else:
  253.                 processor = self.processors.get(fpath.splitext()[1])
  254.             if processor:
  255.                 return (
  256.                     inevow.IResource(processor(fpath.path, self.registry)),
  257.                     segments[1:])
  258.  
  259.         return self.createSimilarFile(fpath.path), segments[1:]
  260.  
  261.     # methods to allow subclasses to e.g. decrypt files on the fly:
  262.     def openForReading(self):
  263.         """Open a file and return it."""
  264.         return self.fp.open()
  265.  
  266.     def getFileSize(self):
  267.         """Return file size."""
  268.         return self.fp.getsize()
  269.  
  270.  
  271.     def renderHTTP(self, ctx):
  272.         """You know what you doing."""
  273.         self.fp.restat()
  274.  
  275.         if self.type is None:
  276.             self.type, self.encoding = getTypeAndEncoding(self.fp.basename(),
  277.                                                           self.contentTypes,
  278.                                                           self.contentEncodings,
  279.                                                           self.defaultType)
  280.  
  281.         if not self.fp.exists():
  282.             return rend.FourOhFour()
  283.  
  284.         request = inevow.IRequest(ctx)
  285.  
  286.         if self.fp.isdir():
  287.             return self.redirect(request)
  288.  
  289.         # fsize is the full file size
  290.         # size is the length of the part actually transmitted
  291.         fsize = size = self.getFileSize()
  292.  
  293.         request.setHeader('accept-ranges','bytes')
  294.  
  295.         if self.type:
  296.             request.setHeader('content-type', self.type)
  297.         if self.encoding:
  298.             request.setHeader('content-encoding', self.encoding)
  299.  
  300.         try:
  301.             f = self.openForReading()
  302.         except IOError, e:
  303.             import errno
  304.             if e[0] == errno.EACCES:
  305.                 return error.ForbiddenResource().render(request)
  306.             else:
  307.                 raise
  308.  
  309.         if request.setLastModified(self.fp.getmtime()) is http.CACHED:
  310.             return ''
  311.  
  312.         try:
  313.             range = request.getHeader('range')
  314.  
  315.             if range is not None:
  316.                 # This is a request for partial data...
  317.                 bytesrange = string.split(range, '=')
  318.                 assert bytesrange[0] == 'bytes',\
  319.                        "Syntactically invalid http range header!"
  320.                 start, end = string.split(bytesrange[1],'-')
  321.                 if start:
  322.                     f.seek(int(start))
  323.                 if end:
  324.                     end = int(end)
  325.                 else:
  326.                     end = fsize-1
  327.                 request.setResponseCode(http.PARTIAL_CONTENT)
  328.                 request.setHeader('content-range',"bytes %s-%s/%s" % (
  329.                     str(start), str(end), str(fsize)))
  330.                 #content-length should be the actual size of the stuff we're
  331.                 #sending, not the full size of the on-server entity.
  332.                 size = 1 + end - int(start)
  333.  
  334.             request.setHeader('content-length', str(size))
  335.         except:
  336.             traceback.print_exc(file=log.logfile)
  337.  
  338.         if request.method == 'HEAD':
  339.             return ''
  340.  
  341.         # return data
  342.         FileTransfer(f, size, request)
  343.         # and make sure the connection doesn't get closed
  344.         return request.deferred
  345.  
  346.     def redirect(self, request):
  347.         return redirectTo(addSlash(request), request)
  348.  
  349.     def listNames(self):
  350.         if not self.fp.isdir():
  351.             return []
  352.         directory = self.fp.listdir()
  353.         directory.sort()
  354.         return directory
  355.  
  356.     def createSimilarFile(self, path):
  357.         f = self.__class__(path, self.defaultType, self.ignoredExts, self.registry)
  358.         # refactoring by steps, here - constructor should almost certainly take these
  359.         f.processors = self.processors
  360.         f.indexNames = self.indexNames[:]
  361.         return f
  362.  
  363.  
  364. class FileTransfer(pb.Viewable):
  365.     """
  366.     A class to represent the transfer of a file over the network.
  367.     """
  368.     request = None
  369.     def __init__(self, file, size, request):
  370.         self.file = file
  371.         self.size = size
  372.         self.request = request
  373.         request.registerProducer(self, 0)
  374.  
  375.     def resumeProducing(self):
  376.         if not self.request:
  377.             return
  378.         data = self.file.read(min(abstract.FileDescriptor.bufferSize, self.size))
  379.         if data:
  380.             self.request.write(data)
  381.             self.size -= len(data)
  382.         if self.size <= 0:
  383.             self.request.unregisterProducer()
  384.             self.request.finish()
  385.             self.request = None
  386.  
  387.     def pauseProducing(self):
  388.         pass
  389.  
  390.     def stopProducing(self):
  391.         self.file.close()
  392.         self.request = None
  393.  
  394.     # Remotely relay producer interface.
  395.  
  396.     def view_resumeProducing(self, issuer):
  397.         self.resumeProducing()
  398.  
  399.     def view_pauseProducing(self, issuer):
  400.         self.pauseProducing()
  401.  
  402.     def view_stopProducing(self, issuer):
  403.         self.stopProducing()
  404.  
  405.  
  406.     synchronized = ['resumeProducing', 'stopProducing']
  407.  
  408. threadable.synchronize(FileTransfer)
  409.  
  410. """I contain AsIsProcessor, which serves files 'As Is'
  411.    Inspired by Apache's mod_asis
  412. """
  413.  
  414. class ASISProcessor:
  415.     implements(inevow.IResource)
  416.     
  417.     def __init__(self, path, registry=None):
  418.         self.path = path
  419.         self.registry = registry or Registry()
  420.  
  421.     def renderHTTP(self, ctx):
  422.         request = inevow.IRequest(ctx)
  423.         request.startedWriting = 1
  424.         return File(self.path, registry=self.registry)
  425.  
  426.     def locateChild(self, ctx, segments):
  427.         return appserver.NotFound
  428.